#define DLL_INDIRECT_PATH

using System;
using System.Drawing;
using System.Drawing.Imaging;
//using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using StaticUtility;

namespace BmgNet
{
	/*
	struct BMGImageStruct
	{
		unsigned int width;
		unsigned int height;
		unsigned char bits_per_pixel;
		unsigned char *bits;
		unsigned short palette_size;
		unsigned char bytes_per_palette_entry;
		unsigned char *palette;
		unsigned int scan_width;
		int opt_for_bmp; //= 1 if memory has been sized for HBITMAP, 0 otherwise
		short transparency_index;
	};
	*/
	public unsafe class UnsafeSupport
	{
		/// <summary>
		/// Compatibility with the BMGLib.
		/// </summary>
		[StructLayout(LayoutKind.Sequential)]
		public struct BMGImageStruct
		{
			public uint width;
			public uint height;
			public byte bits_per_pixel;
			public byte* bits;
			public ushort palette_size;
			public byte bytes_per_palette_entry;
			public byte* palette;
			public uint scan_width;
			public int opt_for_bmp; /*= 1 if memory has been sized for HBITMAP, 0 otherwise*/
			public short transparency_index;
		}
		public static ushort[] StaticMem;
		public static int[] PvCtr = new int[64];
		/// <summary>
		/// Enumeration supporting LibTiff (and ultimately Bmglib.dll).
		/// </summary>
		public enum TiffCompressionEnum 
		{
			NONE, CCITTRLE, CCITTFAX3, CCITTFAX4, LZW, JPEG6,
			JPEG_DCT, NeXT, CCITTRLEW, MACINTOSH, THUNDERSCAN,
			PIXARFILM, PIXARLOG, ZIP, KODAK, JBIG };
		/// <summary>
		/// Enumeration supporting LibTiff (and ultimately Bmglib.dll).
		/// </summary>
		public enum TiffPhotometricEnum 
		{
			MINISWHITE, MINISBLACK, RGB, PALETTE, MASK,
			SEPARATED, YCBCR, CIELAB, CIE_LOGL, CIE_LOGLUV };
		/// <summary>
		/// Enumeration supporting LibTiff (and ultimately Bmglib.dll).
		/// </summary>
		public enum TiffOrientationEnum { TOPLEFT, BOTTOMLEFT };
		/// <summary>
		/// Enumeration supporting LibTiff (and ultimately Bmglib.dll).
		/// </summary>
		public struct TIFFInfoStruct
		{
			public TiffCompressionEnum compression;
			public TiffPhotometricEnum photometric;
			public TiffOrientationEnum orientation;
			public ushort predictor;
		}

		/// <summary>
		/// Imported from Bmglib.dll.  Supported formats and credits associated with the DLL are listed at
		/// http://members.cox.net/scottheiman/bmglib.htm.  This fills the BMGImageStruct with header info
		/// for the PNG file with the given filename.
		/// </summary>
		/// <param name="filename">string  The full path of the image file.</param>
		/// <param name="img">(BMGImageStruct*)  The structure into which the output shall go.</param>
		/// <returns>int</returns>
#if DLL_INDIRECT_PATH
		[DllImport(@"E:\2006_10_03_Objects\BMGLib\BMGLib_DLL\BmgLib.dll", ExactSpelling=true, SetLastError=true)]
#else
        [DllImport("Bmglib.dll", SetLastError=true)]
#endif
		public static extern int GetHeaderFromPngFile(
			[MarshalAs(UnmanagedType.LPStr)] string filename,
			ref BMGImageStruct img );
		public static void GetInfoPng(string filename, out int width, out int height)
		{
			BMGImageStruct img = new BMGImageStruct();
			GetHeaderFromPngFile(filename, ref img);
			width = (int)img.width;
			height = (int)img.height;
		}
#if DLL_INDIRECT_PATH
		[DllImport(@"E:\2006_10_03_Objects\BMGLib\BMGLib_DLL\BmgLib.dll", ExactSpelling=true, SetLastError=true)]
#else
        [DllImport("Bmglib.dll",SetLastError=true)]
#endif
		public static extern int GetDataPng16( [MarshalAs(UnmanagedType.LPStr)] string filename,
			ushort* pixelValuesPointer );

		/// <summary>
		/// Opens a 16-bit PNG file and returns the pixel values as floating point numbers from 0.0f to 65535.0f.
		/// for i=1:width, for j=1:height, index red as i*(*height)+j; index green as width*height+i*(*height)+j;
		/// index blue as 2*width*height+i*(*height)+j;
		/// </summary>
		/// <param name="filename"></param>
		/// <param name="width"></param>
		/// <param name="height"></param>
		/// <returns></returns>
#if DLL_INDIRECT_PATH
		[DllImport(@"E:\2006_10_03_Objects\BMGLib\BMGLib_DLL\BmgLib.dll", ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
#else
        [DllImport("Bmglib.dll",CallingConvention=CallingConvention.StdCall)]
#endif
		public static extern void OPENPNG16(
			[MarshalAs(UnmanagedType.LPStr)] string filename, int* len, 
			int* width, int* height, float* output );
		/// <summary>
		/// 
		/// </summary>
		/// <param name="filename"></param>
		/// <param name="pixelValues">[,,0] is red, [,,1] is green, [,,2] is blue, and [,,3] is alpha.</param>
		/// <returns></returns>
		public static int GetShort(string filename, Ushort3 pixelValues)
		{
			return GetDataPng16(filename, (ushort*)pixelValues.MemLoc.ToPointer());
		}
	}
	public enum ColorSpace { Lms, LmsOpp, LogLms, LogLmsOpp, Xyz };
	public enum ColorDim { LogLmsLum, LogLmsYb, LogLmsRg, LogLmsHue, LogLmsChroma };
	public class KodakColorSpace
	{
		//	Misc
		private static float[] vec = new float[3];
		static KodakColorSpace()
		{
			//	XYZ space
			F[0,0] = 0.5633271f*XyzScalar;
			F[0,1] = 0.4732083f*XyzScalar;
			F[0,2] = 0.1618881f*XyzScalar;

			F[1,0] = -0.430448f*XyzScalar;
			F[1,1] =  1.43644f*XyzScalar;
			F[1,2] = -0.156372f*XyzScalar;

			F[2,0] = -9.75E-02f*XyzScalar;
			F[2,1] = -0.1223779f*XyzScalar;
			F[2,2] = 2.670502f*XyzScalar;


			//	Lms cone space before Krinov
			//G[0,0] = -1.593165E-03f*LmsScalar;
			//G[0,1] =  1.360227E+00f*LmsScalar;
			//G[0,2] =  4.251467E-01f*LmsScalar;

			//G[1,0] = -5.338503E-01f*LmsScalar;
			//G[1,1] =  1.532065E+00f*LmsScalar;
			//G[1,2] =  6.469848E-01f*LmsScalar;

			//G[2,0] = -1.868477E-01f*LmsScalar;
			//G[2,1] =  9.397667E-02f*LmsScalar;
			//G[2,2] =  1.971698E+00f*LmsScalar;

            //  Lms cone space after krinov
            G[0, 0] =  0.0038638f * LmsScalar;
            G[0, 1] =  1.3575f * LmsScalar;
            G[0, 2] =  0.41726f * LmsScalar;

            G[1, 0] = -0.52542f * LmsScalar;
            G[1, 1] =  1.5301f * LmsScalar;
            G[1, 2] =  0.65065f * LmsScalar;

            G[2, 0] = -0.25061f * LmsScalar;
            G[2, 1] =  0.1591f * LmsScalar;
            G[2, 2] =  1.9582f * LmsScalar;
		}
		#region XYZ Space
		//	XYZ Space
		public static float XyzScalar = 0.038827f;		//  683/17590.77 (~candelas)
		public static float[,] F = new float[3,3];
		public static float XyzMin=0.01f;
		public static void RgbToXyz(float[] rgbIn, float[] xyzOut)
		{
			Util.MM(F, rgbIn, xyzOut);
			if(xyzOut[0]<0.0f)
				xyzOut[0]=XyzMin;
			if(xyzOut[1]<0.0f)
				xyzOut[1]=XyzMin;
			if(xyzOut[2]<0.0f)
				xyzOut[2]=XyzMin;
		}
		public static float YToL(float y)
		{
			if(y<=0.008856f)
				return 903.3f*y;
			return (float)(116.0*Math.Pow((double)y, 0.33333333)-16.0);
		}
		#endregion
		#region LMS Cone Space
		public static float LmsScalar = 5.6848e-005f;	//	1/17590.77 (watts)
		//	Lms Cone space
		public static float[,] G = new float[3,3];
		//	After outliiers are removed.
		public static float[] LumEigvec, YbEigvec, RgEigvec, LogLmsMeans;
		public static float LumAddend, RgAddend,  YbAddend, LumSqrtEigval, YbSqrtEigval, RgSqrtEigval;
		public static float LmsMin=0.01f;
		public static void RgbToLms(float[] rgbIn, float[] lmsOut)
		{
			Util.MM(G,rgbIn,lmsOut);
			if(lmsOut[0]<LmsMin)
				lmsOut[0]=LmsMin;
			if(lmsOut[1]<LmsMin)
				lmsOut[1]=LmsMin;
			if(lmsOut[2]<LmsMin)
				lmsOut[2]=LmsMin;
		}
		public static void RgbToLogLms(float[] rgbIn, float[] loglmsOut)
		{
			Util.MM(G,rgbIn,loglmsOut);
			if(loglmsOut[0]<LmsMin)
				loglmsOut[0]=LmsMin;
			if(loglmsOut[1]<LmsMin)
				loglmsOut[1]=LmsMin;
			if(loglmsOut[2]<LmsMin)
				loglmsOut[2]=LmsMin;
			loglmsOut[0] = (float)Math.Log10((double)loglmsOut[0]);
			loglmsOut[1] = (float)Math.Log10((double)loglmsOut[1]);
			loglmsOut[2] = (float)Math.Log10((double)loglmsOut[2]);
		}
		public static void LmsToLumYbRg(float[] lmsIn, float[] lumYbRgOut)
		{			
			lumYbRgOut[0] = lmsIn[0]+lmsIn[1];

			lumYbRgOut[1] = 0.5f*(lmsIn[0]+lmsIn[1])-lmsIn[2];

			lumYbRgOut[2] = lmsIn[0]-lmsIn[1];
		}
		public static void LogLmsToLumYbRg(float[] loglmsIn, float[] lumYbRgOut)
		{
			lumYbRgOut[0] = Math.Max(0.001f, 
				 loglmsIn[0]*0.6158f
				+loglmsIn[1]*0.6113f
				+loglmsIn[2]*0.6193f );

			lumYbRgOut[1] = 
				 loglmsIn[0]*3.5090f
				+loglmsIn[1]*3.4227f
				-loglmsIn[2]*6.8678f
				-1.5452f;

			lumYbRgOut[2] = 
				 loglmsIn[0]*76.6413f
				-loglmsIn[1]*77.6575f
				-loglmsIn[2]*0.4566f
				-2.6725f;
		}
		public static float LumYbRgToHue(float[] lumYbRgIn)
		{
			return (float)Math.Atan2((double)lumYbRgIn[1], (double)lumYbRgIn[2]);
		}
		public static float LumYbRgToChroma(float[] lumYbRgIn)
		{
			return (float)Math.Sqrt( lumYbRgIn[2]*lumYbRgIn[2]+lumYbRgIn[1]*lumYbRgIn[1] );
		}
		public static float LumYbRgToSat(float[] lumYbRgIn)
		{
			return (float)Math.Sqrt( lumYbRgIn[2]*lumYbRgIn[2]+lumYbRgIn[1]*lumYbRgIn[1] ) / lumYbRgIn[0];
		}
		#endregion
	}
	/// <summary>
	/// This houses a 3-dimensional array using COM unmanaged memory to
	/// facilitate the passing of 3-dimensional arrays to a DLL using
	/// COM Interop.
	/// </summary>
	public class Ushort3 : IDisposable
	{
		/// <summary>
		/// Provides convenient indexing for the 3-d array of complex numbers.
		/// Index as [row,col,page or channel].
		/// </summary>
		public ushort this[int i, int j, int k]
		{
			get
			{
				unsafe 
				{
					return *((ushort*)((int)memLoc
						+sizeof(ushort)*k
						+sizeof(ushort)*j*Nk
						+sizeof(ushort)*i*Nj*Nk));
				}
			}
			set
			{
				unsafe
				{
					*((ushort*)((int)memLoc
						+sizeof(ushort)*k
						+sizeof(ushort)*j*Nk
						+sizeof(ushort)*i*Nj*Nk)) = value;
				}
			}
		}
		/// <summary>
		/// Points to memory allocated by Marshal.AllocCoTaskMem for COM
		/// Interoperability.  This contains the 2-d array of complex numbers.
		/// </summary>
		protected IntPtr memLoc = IntPtr.Zero;
		/// <summary>
		/// Points to memory allocated by Marshal.AllocCoTaskMem for COM
		/// Interoperability.  This contains the 2-d array of complex numbers.
		/// </summary>
		public IntPtr MemLoc
		{
			get
			{
				return memLoc;
			}
		}
		/// <summary>
		/// The number of elements for the slowly changing index (number of rows, height)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].
		/// </summary>
		public int Ni;
		/// <summary>
		/// The number of elements for the middle index (number of columns, width)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].
		/// </summary>
		public int Nj;
		/// <summary>
		/// The number of elements for the quickly changing index (number of pages, depth)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].
		/// </summary>
		public int Nk;
		/// <summary>
		/// Allocates COM memory for holding the 2-dimensional array of complex numbers.
		/// The memory is pointed to by memLoc and re-represented by an array of pointers
		/// that is pointed to by fftwMemLoc (for automatic use with fftw3.dll).
		/// </summary>
		/// <param name="Ni">The number of elements for the slowly changing index (number of rows, height)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].</param>
		/// <param name="Nj">The number of elements for the middle index (number of columns, width)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].</param>
		/// <param name="Nk">The number of elements for the quickly changing index (number of pages, depth)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].</param>
		public Ushort3(int Ni, int Nj, int Nk)
		{
			this.Ni = Ni;
			this.Nj = Nj;
			this.Nk = Nk;
			MemoryAlloc(Ni,Nj,Nk);
		}
		/// <summary>
		/// Allocates COM memory for holding the 2-dimensional array of complex numbers.
		/// The memory is pointed to by memLoc and re-represented by an array of pointers
		/// that is pointed to by fftwMemLoc (for automatic use with fftw3.dll).
		/// </summary>
		/// <param name="Ni">The number of elements for the slowly changing index (number of rows, height)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].</param>
		/// <param name="Nj">The number of elements for the middle index (number of columns, width)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].</param>
		/// <param name="Nk">The number of elements for the quickly changing index (number of pages, depth)
		/// [0..Ni-1, 0..Nj-1, 0..Nk-1].</param>
		public void MemoryAlloc(int Ni, int Nj, int Nk)
		{
			//	First dispose of any resources that have already been allocated (prevents memory leak).
			this.Dispose();
			//	Next allocate memory.
			unsafe
			{
				memLoc = Marshal.AllocCoTaskMem(sizeof(ushort)*Ni*Nj*Nk);
			}
			int intMemLoc = (int)memLoc;
		}
		~Ushort3()
		{
			Dispose();
		}
		/// <summary>
		/// Since this class holds unmanaged resources (pointed to by memLoc), 
		/// this method provides the necessary cleanup on disposal.  Cleanup is
		/// automatically handled by .NET because this method implements 
		/// the IDisposable interface.
		/// </summary>
		public void Dispose()
		{
			if( memLoc != IntPtr.Zero )
			{
				Marshal.FreeCoTaskMem( memLoc );
                memLoc = IntPtr.Zero;
			}
		}
		/// <summary>
		/// Makes a bitmap representation of this object.
		/// </summary>
		/// <param name="bmp"></param>
		/// <param name="correct">Applies a display correction for 12-bit linear image.</param>
		public Bitmap AsBitmap(bool correct)
		{
			Bitmap bmp = new Bitmap(Nj, Ni);
			FillBitmap(bmp, correct);
			return bmp;
		}
		/// <summary>
		/// Fills the bitmap
		/// </summary>
		/// <param name="bmp"></param>
		/// <param name="correct">Applies a display correction for 12-bit linear image.</param>
		public void FillBitmap(Bitmap bmp, bool correct)
		{
			int i,j;
			if(correct)
			{
				int ir, ig, ib;
				double dr, dg, db;

				if(Nk>3)	//	do alpha
				{
					int ia;
					double da;

					for(i=0; i<Ni; i++)
						for(j=0; j<Nj; j++)
						{
							dr = Math.Pow((double)this[i,j,0]/4095.0, 0.5);
							dg = Math.Pow((double)this[i,j,1]/4095.0, 0.5);
							db = Math.Pow((double)this[i,j,2]/4095.0, 0.5);
							da = Math.Pow((double)this[i,j,3]/4095.0, 0.5);

							ir = Math.Min(255, (int)(255.0*dr+0.5));
							ig = Math.Min(255, (int)(255.0*dg+0.5));
							ib = Math.Min(255, (int)(255.0*db+0.5));
							ia = Math.Min(255, (int)(255.0*da+0.5));

							bmp.SetPixel(j,i,Color.FromArgb(ia, ir, ig, ib));
						}
				}
				else		//	no alpha
				{
					for(i=0; i<Ni; i++)
						for(j=0; j<Nj; j++)
						{
							dr = Math.Pow((double)this[i,j,0]/4095.0, 0.5);
							dg = Math.Pow((double)this[i,j,1]/4095.0, 0.5);
							db = Math.Pow((double)this[i,j,2]/4095.0, 0.5);

							ir = Math.Min(255, (int)(255.0*dr+0.5));
							ig = Math.Min(255, (int)(255.0*dg+0.5));
							ib = Math.Min(255, (int)(255.0*db+0.5));

							bmp.SetPixel(j,i,Color.FromArgb(ir, ig, ib));
						}
				}
			}
			else
			{
				for(i=0; i<Ni; i++)
					for(j=0; j<Nj; j++)
					{
						bmp.SetPixel(j,i,Color.FromArgb(
							Math.Min(255,(int)this[i,j,3]), 
							Math.Min(255,(int)this[i,j,0]), 
							Math.Min(255,(int)this[i,j,1]), 
							Math.Min(255,(int)this[i,j,2])) );
					}
			}
		}
		/// <summary>
		/// Returns a Ushort3 object with data from a 16-bit per pixel PNG file.
		/// Nk will automatically equal 4, even if the PNG file does not contain an alpha channel.
		/// [,,0] is red, [,,1] is green, [,,2] is blue, [,,3] is alpha.
		/// </summary>
		/// <param name="filename"></param>
		/// <returns></returns>
		public static Ushort3 FromPng16(string filename, out int errMsg)
		{
			int ni, nj;
			UnsafeSupport.GetInfoPng(filename, out nj, out ni);
			Ushort3 output = new Ushort3(ni, nj, 4);
			unsafe
			{
				errMsg = UnsafeSupport.GetDataPng16(filename, (ushort*)(output.MemLoc.ToPointer()) );
			}
			return output;
		}
		/// <summary>
		/// Returns a Ushort3 object .
		/// [,,0] is red, [,,1] is green, [,,2] is blue, [,,3] is alpha.
		/// </summary>
		/// <param name="filename"></param>
		/// <returns></returns>
		public static Ushort3 FromImage(string filename)
		{
			Bitmap bmp = new Bitmap(filename);
			Ushort3 output = new Ushort3(bmp.Height, bmp.Width, 4);
			int i,j;
			Color col;
			for(i=0; i<bmp.Height; i++)
				for(j=0; j<bmp.Width; j++)
				{
					col = bmp.GetPixel(j,i);
					output[i,j,0] = (ushort)col.R;
					output[i,j,1] = (ushort)col.G;
					output[i,j,2] = (ushort)col.B;
					output[i,j,3] = (ushort)col.A;
				}
			return output;
		}
		/// <summary>
		/// Enter floating point values into the array provided.  The array must be allocated to the appropriate size.
		/// </summary>
		/// <param name="output">The array where floating point values are entered.</param>
		public void ToFloat(float[,,] output)
		{
			int i,j;
			if(Nk==3)	//	Save time on common situation
				for(i=0; i<Ni; i++)
					for(j=0; j<Nj; j++)
					{
						output[i,j,0] = (float)this[i,j,0];
						output[i,j,1] = (float)this[i,j,1];
						output[i,j,2] = (float)this[i,j,2];
					}
			else if(Nk==4)	//	Save time on common situation
				for(i=0; i<Ni; i++)
					for(j=0; j<Nj; j++)
					{
						output[i,j,0] = (float)this[i,j,0];
						output[i,j,1] = (float)this[i,j,1];
						output[i,j,2] = (float)this[i,j,2];
						output[i,j,3] = (float)this[i,j,3];
					}
			else	//	Save time fallback
			{
				for(int k=0; k<Nk; k++)
					for(i=0; i<Ni; i++)
						for(j=0; j<Nj; j++)
							output[i,j,k] = (float)this[i,j,k];
			}
		}        
	}
}